﻿using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using xuni;

namespace xres
{


    /// <summary>
    /// 资源热更
    /// </summary>
    public class ResUpdateCheckAction : BaseAsyncAction
    {

        public const string Err_Load_External_Versions = "Err_Load_External_Versions";

        public const string Err_Server_ResVer_Network = "Err_Server_ResVer_Network";
        public const string Err_Server_ResVer_Http = "Err_Server_ResVer_Http";

        public const string Err_Server_Versions_Network = "Err_Server_Versions_Network";
        public const string Err_Server_Versions_Http = "Err_Server_Versions_Http";

        public const string Result_No_Update = "Result_No_Update";
        public const string Result_Down_Verify_Fail_Files = "Result_Down_Verify_Fail_Files";
        public const string Result_Down_Files = "Result_Down_Files";

        string _baseResUrl = "http://localhost:8081/Windows/";

        public string Result { get; private set; }

        Versions _externalVersions;
        Versions _serverVersions;

        public readonly List<FileVersion> toDownloadFiles = new List<FileVersion>();


        public string GetTotalDownSize()
        {
            long total = 0;
            for (var i = 0; i < toDownloadFiles.Count; ++i)
            {
                total += toDownloadFiles[i].len;
            }

            return IOUtils.Byte2ReadableUnit(total);
        }

        public float CheckProgress()
        {
            switch (_state)
            {
            case BaseAsyncActionState.Init:
                return 0;

                /*
            case "Check":
                return 0.01f;
                */

            }

            return 1;
        }

        public void Check()
        {
            switch (_state)
            {
            case BaseAsyncActionState.Init:
                break;

            case BaseAsyncActionState.Finish:
            {
                Result = "";
                _externalVersions = null;
                _serverVersions = null;
                toDownloadFiles.Clear();
                break;
            }

            default:
                return;
            }

            _state = "Check";

            var externalPath = ResPath.BuildExternalBundlePath("Versions.bin");
            if (File.Exists(externalPath))
            {
                Debug.Log($"CheckResUpdate: Versions eixst");
                try
                {
                    _externalVersions = Versions.CreateFromFile(externalPath);

                    var url = xuni.StringUtils.Builder.Append(_baseResUrl).Append("ResVer.bin").BuildString();

                    //请求服务器的ResVer.bin
                    var act = new GetTextAction();
                    act.onComplete.AddListener(OnComplete_ServerResVer);
                    act.GetText(url, true);
                }
                catch (Exception ex) //文件读取失败
                {
                    Debug.LogError($"CheckResUpdate: msg: {ex.Message}");

                    SetError(Err_Load_External_Versions, $"external Versions file load fail");
                    SetFinishAndNotify(ErrMsg);
                    return;
                }
            }
            else
            {
                Debug.Log($"CheckResUpdate: Versions not eixst");
                //直接请求服务器的Versions.bin
                RequestServerVersions();
            }
        }

        void OnComplete_ServerResVer(object obj, string type, object data)
        {
            var action = (GetTextAction)obj;
            if (!string.IsNullOrEmpty(action.ErrType))
            {
                switch (action.ErrType)
                {
                case GetTextAction.Err_Http:
                    SetError(Err_Server_ResVer_Http, action.ErrMsg);
                    break;

                case GetTextAction.Err_Network:
                    SetError(Err_Server_ResVer_Network, action.ErrMsg);
                    break;
                }

                if (false) Debug.LogError(ErrMsg);
                SetFinishAndNotify(ErrMsg);
                return;
            }

            try
            {
                var serverResVer = int.Parse(action.Text);
                if (serverResVer <= _externalVersions.resVersion)
                {
                    Debug.Log($"ResVer: server: {serverResVer}, local: {_externalVersions.resVersion}");
                    VerifyLocalFiles();
                    return;
                }
            }
            catch (Exception ex)
            {
                SetError(Err_Server_ResVer_Http, $"server ResVer parse fail");
                Debug.LogError(ErrMsg);
                SetFinishAndNotify(ErrMsg);
                return;
            }

            RequestServerVersions();
        }

        /// <summary>
        /// 不需要热更时, 简单校验下文件是否被篡改或丢失, 如果存在这样的情况, 则通过热更来修复
        /// </summary>
        void VerifyLocalFiles()
        {
            var externalFiles = _externalVersions.files;
            foreach (var entry in externalFiles)
            {
                var fv = entry.Value;
                var filePath = ResPath.BuildExternalBundlePath(fv.path);
                if (!File.Exists(filePath))
                {
                    Debug.Log($"VerifyLocalFiles: file not exists: {fv.path}");
                    toDownloadFiles.Add(fv);
                    continue;
                }

                var fi = new FileInfo(filePath);
                if (fv.len != fi.Length)
                {
                    Debug.Log($"VerifyLocalFiles: file length not match: {fv.path}");
                    toDownloadFiles.Add(fv);
                    continue;
                }

                //todo: 检查crc
            }

            if (toDownloadFiles.Count > 0)
            {
                Result = Result_Down_Verify_Fail_Files;
                SetError("", "");
                SetFinishAndNotify("");
            }
            else
            {
                Debug.Log($"CheckResUpdate: verify ok, no need update");

                Result = Result_No_Update;
                SetError("", "");
                SetFinishAndNotify("");
            }
        }


        /// <summary>
        /// 需要热更时, 获取详细的文件列表来确定哪些文件需要更新
        /// </summary>
        void RequestServerVersions()
        {
            var url = xuni.StringUtils.Builder.Append(_baseResUrl).Append("Versions.bin").BuildString();

            //请求服务器的 Versions.bin
            var act = new GetTextAction();
            act.onComplete.AddListener(OnComplete_ServerVersions);
            act.GetText(url, true);
        }

        void OnComplete_ServerVersions(object obj, string type, object data)
        {
            var action = (GetTextAction)obj;
            if (!string.IsNullOrEmpty(action.ErrType))
            {
                switch (action.ErrType)
                {
                case GetTextAction.Err_Http:
                    SetError(Err_Server_Versions_Http, action.ErrMsg);
                    break;

                case GetTextAction.Err_Network:
                    SetError(Err_Server_Versions_Network, action.ErrMsg);
                    break;
                }

                Debug.LogError(ErrMsg);
                SetFinishAndNotify(ErrMsg);
                return;
            }

            _serverVersions = null;
            Versions serverVersions;
            try
            {
                serverVersions = Versions.CreateFromString(action.Text);
                _serverVersions = serverVersions;
            }
            catch (Exception ex)
            {
                Debug.LogError($"msg: {ex.Message}, st: {ex.StackTrace}");

                SetError(Err_Server_Versions_Http, $"server Versions parse fail");
                SetFinishAndNotify(ErrMsg);
                return;
            }

            _state = "CheckToDownFiles";
            ThreadPool.QueueUserWorkItem(CheckToDownFilesOnThread);
        }

        void CheckToDownFilesOnThread(object state)
        {
            var crc32Table = Crc32Utils.CreateCrcTable();

            if (null == _externalVersions) //完整下载
            {
                Debug.Log($"CheckResUpdate: full download");
                foreach (var entry in _serverVersions.files)
                {
                    var serverFv = entry.Value;
                    CheckLocalFileExists(serverFv, crc32Table);
                }
            }
            else
            {
                Debug.Log($"CheckResUpdate: download some");

                var localFilesDict = _externalVersions.files;
                var serverFilesDict = _serverVersions.files;
                foreach (var entry in serverFilesDict)
                {
                    var serverFv = entry.Value;

                    if (localFilesDict.TryGetValue(entry.Key, out var externalFv)) //老资源
                    {
                        /*
                        if (serverFv.crc != externalFv.crc) //文件变动了(下载最新的文件)
                        {
                            toDownloadFiles.Add(serverFv);
                        }
                        else //没变动的文件(校验下磁盘上的实际文件)
                        {
                            CheckLocalFileExists(sb, serverFv);
                        }
                        */
                        CheckLocalFileExists(serverFv, crc32Table);

                        localFilesDict.Remove(entry.Key);
                    }
                    else //新增的资源
                    {
                        CheckLocalFileExists(serverFv, crc32Table);
                    }
                }
            }

            MainThreadSync.QueueOnMainThread(OnComplete_CheckToDownFiles);
        }

        void CheckLocalFileExists(FileVersion serverFv, uint[] crc32Table)
        {
            var filePath = ResPath.BuildExternalBundlePath(serverFv.path);
            if (!File.Exists(filePath))
            {
                toDownloadFiles.Add(serverFv);
            }
            else //实际文件存在了(比如: Versions里的数据被修改)
            {
                var fi = new FileInfo(filePath);
                if (serverFv.len != fi.Length)
                {
                    toDownloadFiles.Add(serverFv);
                    if (true) Debug.Log($"CheckResUpdate: local exists, size not equals: server:{IOUtils.Byte2ReadableUnit(serverFv.len)}, local:{IOUtils.Byte2ReadableUnit(fi.Length)}, {serverFv.path}");
                }
                else
                {
                    var crc32 = Crc32Utils.FileCrc32(filePath, crc32Table, null);
                    if (serverFv.crc != crc32)
                    {
                        File.Delete(filePath); //旧文件crc32不一致就先删除
                        toDownloadFiles.Add(serverFv);
                        if (true) Debug.Log($"CheckResUpdate: local exists, crc32 not equals: server:{serverFv.crc}, local:{crc32}");
                    }
                }
            }

        }

        void OnComplete_CheckToDownFiles(object obj)
        {
            Result = Result_Down_Files;
            SetError("", "");
            SetFinishAndNotify("");
        }

        public void SaveVersions()
        {
            if (true) Debug.Log($"SaveVersions");

            if (Result_Down_Verify_Fail_Files == Result)
                return;

            try
            {
                Versions.WriteOut(_serverVersions, ResPath.BuildExternalBundlePath("Versions.bin"));

                //需要删除的老资源
                var serverNotExitsFilesDict = _externalVersions.files;
                Debug.Log($"server has delete: {serverNotExitsFilesDict.Count}");
                foreach (var entry in serverNotExitsFilesDict)
                {
                    var fv = entry.Value;
                    var bundleFilePath = ResPath.BuildExternalBundlePath(fv.path);
                    if (File.Exists(bundleFilePath))
                    {
                        Debug.Log($"server has delete: {fv.path}");
                        File.Delete(bundleFilePath);
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.LogError($"SaveVersions: msg: {ex.Message}");
            }
        }

    } //end of class


}

